package io.elegant.widgets

import android.annotation.SuppressLint
import android.graphics.Color
import android.view.MotionEvent
import android.view.View
import android.view.ViewGroup
import androidx.core.view.marginLeft
import androidx.core.view.marginTop
import com.google.protobuf.Any
import io.elegant.sessions.BaseSession
import io.elegant.toAny
import io.elegant.toBoolValue
import io.elegant.toIntValue
import io.elegant.toStringValueOrNull
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.launch
import kotlin.math.hypot
import kotlin.reflect.KClass


open class BaseWidget<T: View> (
    private val session: BaseSession,
    private val group: ViewGroup,
    private val cs: CoroutineScope
) {
    protected var view: T? = null
    open fun createView(clazz: KClass<T>, vararg argv: Any): T? = null
    open fun create(clazz: KClass<T>, sessionId: Long, futureId: Long, vararg argv: Any) {
        val ctx = group.context
        val d = ctx.resources.displayMetrics.density
        view = createView(clazz, *argv)
        if (view == null)
            view = clazz.constructors.first().call(ctx)
        val w = argv[0].toIntValue() * d
        val h = argv[1].toIntValue() * d
        val x = argv[2].toIntValue() * d
        val y = argv[3].toIntValue() * d
        view?.layoutParams = ViewGroup.MarginLayoutParams(w.toInt(), h.toInt()).apply {
            setMargins(x.toInt(), y.toInt(), w.toInt(), h.toInt())
        }
        group.addView(view)
    }
    open fun destroy() {
        group.removeView(view)
    }
    open fun onReceive(name: String, sessionId: Long, futureId: Long, vararg argv: Any): Boolean {
        return when (name) {
            "click" -> click()
            "focus" -> focus()
            "getAccessibilityLabel" -> getAccessibilityLabel(sessionId, futureId)
            "setAccessibilityLabel" -> setAccessibilityLabel(*argv)
            "setLocation" -> setLocation(*argv)
            "setBackgroundColor" -> setBackgroundColor(*argv)
            "getRegion" -> getRegion(sessionId, futureId)
            "setOnClickListener" -> setOnClickListener(*argv)
            "setOnLongClickListener" -> setOnLongClickListener(*argv)
            "setOnGestureListener" -> setOnGestureListener(*argv)
            "isEnabled" -> isEnabled(sessionId, futureId)
            "setEnabled" -> setEnabled(*argv)
            else -> null
        } != null
    }
    private fun setOnClickListener(vararg argv: Any) = view?.setOnClickListener {
        session.apply {
            cs.launch {
                argv[0].sendCallback()
            }
        }
    }
    private fun setOnLongClickListener(vararg argv: Any) = view?.setOnLongClickListener {
        session.apply {
            cs.launch {
                argv[0].sendCallback()
            }
        }
        true
    }
    @SuppressLint("ClickableViewAccessibility")
    @Suppress("DEPRECATION")
    private fun setOnGestureListener(vararg argv: Any) = session.apply {
        val moveInterval = if (argv.size > 1) argv[1].toIntValue()
        else 20
        val d = group.context.resources.displayMetrics.density
        var oldX = 0f
        var oldY = 0f
        fun touch(event: MotionEvent): Boolean {
            val x = event.x / d
            val y = event.y / d
            when(event.action) {
                MotionEvent.ACTION_DOWN, MotionEvent.ACTION_POINTER_2_DOWN -> {
                    oldX = x
                    oldY = y
                    cs.launch {
                        argv[0].sendCallback(oldX.toInt().toAny(), oldY.toInt().toAny(), true.toAny(), event.pointerCount.toAny())
                    }
                }
                MotionEvent.ACTION_MOVE -> {
                    if (hypot(oldX -x, oldY -y) < moveInterval)
                        return true
                    oldX = x
                    oldY = y
                    cs.launch {
                        argv[0].sendCallback(oldX.toInt().toAny(), oldY.toInt().toAny(), true.toAny(), event.pointerCount.toAny())
                    }
                }
                MotionEvent.ACTION_UP, MotionEvent.ACTION_POINTER_2_UP, MotionEvent.ACTION_CANCEL -> cs.launch {
                    argv[0].sendCallback(oldX.toInt().toAny(), oldY.toInt().toAny(), false.toAny(), event.pointerCount.toAny())
                }
            }
            return true
        }
        view?.setOnTouchListener { _, event ->
            touch(event)
        }
    }.let { }
    private fun click() = view?.performClick()?.let { }
    private fun focus() = view?.requestFocus()?.let { }
    private fun getRegion(sessionId: Long, futureId: Long) {
        val d = group.context.resources.displayMetrics.density
        val (w, h) = view?.layoutParams?.let {
            (it.width / d).toInt() to (it.height / d).toInt()
        } ?: (0 to 0)
        val (x, y) = view?.let {
            (it.marginLeft / d).toInt() to (it.marginTop / d).toInt()
        } ?: (0 to 0)
        onGetRegion(w.toAny(), h.toAny(), x.toAny(), y.toAny(), sessionId, futureId)
    }
    private fun onGetRegion(width: Any, height: Any, x: Any, y: Any, sessionId: Long, futureId: Long) = session.apply {
        sendAction("onGetRegion", sessionId, futureId, width, height, x, y)
    }
    private fun getAccessibilityLabel(sessionId: Long, futureId: Long) {
        val label = view?.contentDescription?.toString()
        onGetAccessibilityLabel(label.toAny(), sessionId, futureId)
    }
    private fun onGetAccessibilityLabel(label: Any, sessionId: Long, futureId: Long) = session.apply {
        sendAction("onGetAccessibilityLabel", sessionId, futureId, label)
    }
    private fun setAccessibilityLabel(vararg argv: Any) {
        view?.contentDescription = argv[0].toStringValueOrNull()
    }
    private fun setLocation(vararg argv: Any) {
        val lp = view?.layoutParams ?: return
        val d = group.context.resources.displayMetrics.density
        view?.layoutParams = (if (lp is ViewGroup.MarginLayoutParams) lp
        else ViewGroup.MarginLayoutParams(lp.width, lp.height)).apply {
            val x = argv[0].toIntValue() * d
            val y = argv[1].toIntValue() * d
            setMargins(x.toInt(), y.toInt(), lp.width, lp.height)
        }
    }
    private fun setBackgroundColor(vararg argv: Any) {
        val color = argv[0].toStringValueOrNull()
        if (color.isNullOrBlank()) {
            view?.background = null
            return
        }
        view?.setBackgroundColor(Color.parseColor(color))
    }
    private fun isEnabled(sessionId: Long, futureId: Long) {
        val enabled = view?.isEnabled ?: false
        onIsEnabled(enabled.toAny(), sessionId, futureId)
    }
    private fun onIsEnabled(enabled: Any, sessionId: Long, futureId: Long) = session.apply {
        sendAction("onIsEnabled", sessionId, futureId, enabled)
    }
    private fun setEnabled(vararg argv: Any) {
        view?.isEnabled = argv[0].toBoolValue()
    }
}